#include "ShadowManager.h"

#include "OpenGLHelpers.h" // glu replecements

void ShadowManager::init(int screenWid, int screenHei)
{
	g_LightPosition[0] = 0;
	g_LightPosition[1] = 2;
	g_LightPosition[2] = 5;
	g_LightPosition[3] = 0;

	g_LightView[0] = 0;
	g_LightView[1] = 0;
	g_LightView[2] = 0;

	offsetFactor = 8;
	offsetUnits = 2;
	this->screenWid = screenWid;
	this->screenHei = screenHei;
	shadowWid = min( screenWid, screenHei );
	shadowHei = shadowWid;

	glEnable(GL_LIGHT0);
	GLfloat light_amb[] = { 0.2f, 0.2f, 0.2f, 1.0f };
	GLfloat light_dif[] = { 0.8f, 0.8f, 0.8f, 1.0f };
	GLfloat light_spc[] = { 0.5f, 0.5f, 0.5f, 1.0f };
	//GLfloat light_position[] = {-1.0, 1.0, 10.0, 1.0};  /* Infinite light location. */


	/* Enable a single OpenGL light. */
	glLightfv(GL_LIGHT0, GL_AMBIENT, light_amb);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, light_dif);
	glLightfv(GL_LIGHT0, GL_SPECULAR, light_spc);
	glLightfv(GL_LIGHT0, GL_POSITION, g_LightPosition);


	frameBufferObject.initShadowFBO(shadowWid,shadowHei);
	// Here we allocate memory for our depth texture that will store our light's view
	//CreateRenderTexture(shadowWid, shadowHei, GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT);

	////g_Shader.InitShaders("ShadowMapping.vert", "ShadowMapping.frag");
	g_Shader.InitShaders("fabianShadowMap.vert", "fabianShadowMap.frag");
	g_Shader.TurnOn();
	shadowMapUniform = glGetUniformLocation(g_Shader.GetProgram(),"ShadowMap");
	g_Shader.TurnOff();

	g_Shader.TurnOn();
	glUniform1i(shadowMapUniform,7 );
	g_Shader.TurnOff();

}

void ShadowManager::UpdateShadowLightMatrices()
{	
	glusLookAtf(g_mModelView, g_LightPosition[0],  g_LightPosition[1],  g_LightPosition[2], g_LightView[0],	   g_LightView[1],	    g_LightView[2],		0, 1, 0);
	glusPerspectivef(g_mProjection, 90.0f, 1.0f, 1.0f, 25.0f);
}

void ShadowManager::setupMatrices(float position_x,float position_y,float position_z,float lookAt_x,float lookAt_y,float lookAt_z)
{
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glusPerspective(45,screenWid/screenHei,1,500);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glusLookAt(position_x,position_y,position_z,lookAt_x,lookAt_y,lookAt_z,0,1,0);
}

void ShadowManager::CreateRenderTexture(int sizeX, int sizeY, int channels, int type)										
{
	// Create a pointer to store the blank image data
	unsigned int *pTexture = NULL;											

	// Store the current channels to be allocated by default
	int channelsTrue = channels;

	// If the channels are greater than 4 there must be a special flag like
	// GL_DEPTH_COMPONENT, so make sure we only allocate 1 bit per pixel.
	if(channels > 4)
		channelsTrue = 1;

	// Allocate and init memory for the image array and point to it from pTexture
	pTexture = new unsigned int [sizeX * sizeY * channelsTrue];
	memset(pTexture, 0, sizeX * sizeY * channelsTrue * sizeof(unsigned int));	

	// Register the texture with OpenGL and bind it to the texture ID
	glGenTextures(1, &shadowMapTexture);								
	glBindTexture(GL_TEXTURE_2D, shadowMapTexture);					

	// Create the texture and store it on the video card
	glTexImage2D(GL_TEXTURE_2D, 0, channels, sizeX, sizeY, 0, type, GL_UNSIGNED_INT, pTexture);						

	// Set the texture quality
	glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
	glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

	// Since we stored the texture space with OpenGL, we can delete the image data
	delete [] pTexture;																					
}

void ShadowManager::beginFirstPass()
{
	//First step: Render from the light POV to a FBO, story depth values only
	glBindFramebuffer(GL_FRAMEBUFFER,frameBufferObject.getId() );	//Rendering offscreen

	//Using the fixed pipeline to render to the depthbuffer
	glUseProgram(0);

	// In the case we render the shadowmap to a higher resolution, the viewport must be modified accordingly.
	glViewport(0,0,shadowWid,shadowHei);

	// Clear previous frame values
	glClear( GL_DEPTH_BUFFER_BIT );

	//Disable color rendering, we only want to write to the Z-Buffer
	glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); 

	setupMatrices(g_LightPosition[0],g_LightPosition[1],g_LightPosition[2],g_LightView[0],g_LightView[1],g_LightView[2]);

	// Culling switching, rendering only backface, this is done to avoid self-shadowing
	glCullFace(GL_FRONT);
}

void ShadowManager::endFirstPass()
{

}

void ShadowManager::beginLastPass(float inverseCameraMat[16])
{
	memcpy( g_mCameraInverse, inverseCameraMat, sizeof(float) * 16);
	setTextureMatrix();
	
	// Now rendering from the camera POV, using the FBO to generate shadows
	glBindFramebuffer(GL_FRAMEBUFFER,0);

	glViewport(0,0,screenWid,screenHei);

	////Enabling color write (previously disabled for light POV z-buffer rendering)
	glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); 

	//// Clear previous frame values
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	////Using the shadow shader
	g_Shader.TurnOn();
	glUniform1i(shadowMapUniform,7);
	glActiveTexture(GL_TEXTURE7);
	glBindTexture(GL_TEXTURE_2D,frameBufferObject.depth_rb);

	////setupMatrices(g_mModelView[11],g_mModelView[12],g_mModelView[13],l_camera[0],l_camera[1],l_camera[2]);
	glCullFace(GL_BACK);
}

void ShadowManager::endLastPass()
{
	g_Shader.TurnOff();
}

void ShadowManager::setTextureMatrix()
{
	// remember:
	// mProjectedTexture = mLightProjection * mLightModelview * mCameraInverse;

	// This is matrix transform every coordinate x,y,z
	// x = x* 0.5 + 0.5 
	// y = y* 0.5 + 0.5 
	// z = z* 0.5 + 0.5 
	// Moving from unit cube [-1,1] to [0,1]  
	const float bias[16] = {	
		0.5, 0.0, 0.0, 0.0, 
		0.0, 0.5, 0.0, 0.0,
		0.0, 0.0, 0.5, 0.0,
		0.5, 0.5, 0.5, 1.0};

		glMatrixMode(GL_TEXTURE);
		glActiveTextureARB(GL_TEXTURE7);

		// concatating all matrice into one.
		float result[16];
		glusMultMatrixf( result, bias, g_mProjection);
		glusMultMatrixf( result, result, g_mModelView);
		//glusMultMatrixf( result, result, g_mCameraInverse);
		glLoadMatrixf( result ); // kunne sende til shader med uniform

		// Go back to normal matrix mode
		glMatrixMode(GL_MODELVIEW);
}

// During translation, we also have to maintain the GL_TEXTURE8, used in the shadow shader
// to determine if a vertex is in the shadow.
void ShadowManager::startTranslate(float x,float y,float z)
{
	glPushMatrix();
	glTranslatef(x,y,z);

	glMatrixMode(GL_TEXTURE);
	glActiveTexture(GL_TEXTURE7);
	glPushMatrix();
	glTranslatef(x,y,z);
}

void ShadowManager::endTranslate()
{
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
	glPopMatrix();
}

void ShadowManager::startRotate(float angle, float x,float y,float z)
{
	glPushMatrix();
	glRotatef(angle, x,y,z);

	glMatrixMode(GL_TEXTURE);
	glActiveTexture(GL_TEXTURE7);
	glPushMatrix();
	glRotatef(angle,x,y,z);
}

void ShadowManager::endRotate()
{
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
	glPopMatrix();
}

void ShadowManager::debugRender()
{
	// DEBUG only. this piece of code draw the depth buffer onscreen
	 glUseProgram(0);
	 glMatrixMode(GL_PROJECTION);
	 glLoadIdentity();
	 glOrtho(-screenWid/2,screenWid/2,-screenHei/2,screenHei/2,1,20);
	 glMatrixMode(GL_MODELVIEW);
	 glLoadIdentity();
	 glColor4f(1,1,1,1);
	 glActiveTexture(GL_TEXTURE0);
	 glBindTexture(GL_TEXTURE_2D,frameBufferObject.depth_rb);
	 glEnable(GL_TEXTURE_2D);
	 glTranslated(0,0,-1);
	 glBegin(GL_QUADS);
	 glTexCoord2d(0,0);glVertex3f(0.0f,0.0f,0.0f);
	 glTexCoord2d(1,0);glVertex3f(screenWid/4.0f,0.0f,0.0f);
	 glTexCoord2d(1,1);glVertex3f(screenWid/4.0f,screenHei/4.0f,0.0f);
	 glTexCoord2d(0,1);glVertex3f(0.0f,screenHei/4.0f,0.0f);

	 glEnd();
	 glDisable(GL_TEXTURE_2D);
	
}